//-----------------------------------------------------------------------------
//
// @file	OpenGLRender.cpp
// @brief	Class to use opengl render
// @author	Vinod Melapudi
// @date	20Feb2010
//
//-----------------------------------------------------------------------------

#include <stdio.h>

#include "OpenGLRender.h"
#include "FileOps.h"
#include "glext.h"

//-----------------------------------------------------------------------------

// Extension fn pointers from glext.h
PFNGLGENBUFFERSARBPROC		glGenBuffersARB		= NULL;
PFNGLBINDBUFFERARBPROC		glBindBufferARB		= NULL;
PFNGLBUFFERDATAARBPROC		glBufferDataARB		= NULL;
PFNGLDELETEBUFFERSARBPROC	glDeleteBuffersARB	= NULL;
PFNGLISBUFFERARBPROC		glIsBufferARB		= NULL;


#ifdef SHADER_SUPPORT
PFNGLCREATESHADERPROC		glCreateShader		= NULL;
PFNGLSHADERSOURCEPROC		glShaderSource		= NULL;
PFNGLCOMPILESHADERPROC		glCompileShader		= NULL;
PFNGLCREATEPROGRAMPROC		glCreateProgram		= NULL;
PFNGLATTACHSHADERPROC		glAttachShader		= NULL;
PFNGLLINKPROGRAMPROC		glLinkProgram		= NULL;
PFNGLUSEPROGRAMPROC			glUseProgram		= NULL;
PFNGLDELETESHADERPROC		glDeleteShader		= NULL;
PFNGLDELETEPROGRAMPROC		glDeleteProgram		= NULL;
PFNGLISSHADERPROC			glIsShader			= NULL;
PFNGLISPROGRAMPROC			glIsProgram			= NULL;
#endif

//-----------------------------------------------------------------------------



//
// @brief	Constructor
//
OpenGLRender::OpenGLRender()
{
	m_initialized	= true;

	m_HWnd			= NULL;
	m_device_ctx	= NULL; 
	m_render_ctx	= NULL;
}

//
// @brief	Destructor
//
OpenGLRender::~OpenGLRender()
{
}


//
// @brief	Create rendering context and initialize scene
//
bool OpenGLRender::Init( HWND hWnd, int width, int height )
{
	m_HWnd	= hWnd;

	if( hWnd != NULL && InitRenderDeviceCtx() )
	{
		m_initialized = true;
	}
	else
	{
		UnInit();
	}


	// Extension fn pointers from glext.h
	glGenBuffersARB		= (PFNGLGENBUFFERSARBPROC) wglGetProcAddress("glGenBuffersARB");
	glBindBufferARB		= (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB");
	glBufferDataARB		= (PFNGLBUFFERDATAARBPROC) wglGetProcAddress("glBufferDataARB");
	glDeleteBuffersARB	= (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress("glDeleteBuffersARB");
	glIsBufferARB		= (PFNGLISBUFFERARBPROC) wglGetProcAddress("glIsBufferARB");


#ifdef SHADER_SUPPORT
	glCreateShader		= (PFNGLCREATESHADERPROC) wglGetProcAddress("glCreateShader");
	glShaderSource		= (PFNGLSHADERSOURCEPROC) wglGetProcAddress("glShaderSource");
	glCompileShader		= (PFNGLCOMPILESHADERPROC) wglGetProcAddress("glCompileShader");
	glCreateProgram		= (PFNGLCREATEPROGRAMPROC) wglGetProcAddress("glCreateProgram");
	glAttachShader		= (PFNGLATTACHSHADERPROC) wglGetProcAddress("glAttachShader");
	glLinkProgram		= (PFNGLLINKPROGRAMPROC) wglGetProcAddress("glLinkProgram");
	glUseProgram		= (PFNGLUSEPROGRAMPROC) wglGetProcAddress("glUseProgram");
	glDeleteShader		= (PFNGLDELETESHADERPROC) wglGetProcAddress("glDeleteShader");
	glDeleteProgram		= (PFNGLDELETEPROGRAMPROC) wglGetProcAddress("glDeleteProgram");
	glIsShader			= (PFNGLISSHADERPROC) wglGetProcAddress("glIsShader");
	glIsProgram			= (PFNGLISPROGRAMPROC) wglGetProcAddress("glIsProgram");
#endif

	return m_initialized;
}

//
// @brief	Render in back buffer
//
void OpenGLRender::PreRender()
{
	if( !m_initialized )
		return;
}

void OpenGLRender::PostRender()
{
	if( !m_initialized )
		return;

	// Display back buffer to screen.
	SwapBuffers( m_device_ctx );
}

//
// @brief	Uninitialize contexts and scene
//
void OpenGLRender::UnInit()
{	
	UnInitRenderDeviceCtx();

	m_HWnd	= NULL;
	m_initialized = false;
}







//
// @brief	Setup render device context
//
bool OpenGLRender::InitRenderDeviceCtx()
{
	PIXELFORMATDESCRIPTOR pfd;
	{
		memset( &pfd, 0, sizeof(PIXELFORMATDESCRIPTOR) );

		pfd.nSize		= sizeof(PIXELFORMATDESCRIPTOR);
		pfd.nVersion	= 1;
		pfd.dwFlags		= PFD_SUPPORT_OPENGL |		// OpenGL support
						  PFD_DOUBLEBUFFER |		// Double buffering
						  PFD_DRAW_TO_WINDOW;		// Pixel format for window
		pfd.iPixelType	= PFD_TYPE_RGBA;			// RGBA
		pfd.cColorBits	= 32;						// 24-bit color buffer
		pfd.cDepthBits	= 32;						// 32-bit depth buffer
		pfd.iLayerType	= PFD_MAIN_PLANE;
		
		pfd.cRedBits = pfd.cGreenBits = pfd.cBlueBits = pfd.cAlphaBits = 8;
	}

	if( !(m_device_ctx = GetDC( m_HWnd )) )
		return false;

	int pixelFormat;
	if( !(pixelFormat = ChoosePixelFormat(m_device_ctx, &pfd)) )
		return false;
			
	if( !SetPixelFormat(m_device_ctx, pixelFormat, &pfd) )
		return false;
			
	if( !(m_render_ctx = wglCreateContext(m_device_ctx)) )
		return false;
			
	if( !wglMakeCurrent(m_device_ctx, m_render_ctx) )	
		return false;

	// Setup font bitmap
	if ( !(m_text_list = glGenLists(256)) )
		return false;

	SelectObject( m_device_ctx, GetStockObject(SYSTEM_FONT) );
	wglUseFontBitmaps( m_device_ctx, 0, 255, m_text_list );

	return true;
}


//
// @brief	Release device and render contexts
//
void OpenGLRender::UnInitRenderDeviceCtx()
{
	if( m_HWnd != NULL && m_device_ctx != NULL )
	{
		wglMakeCurrent( m_device_ctx, 0 );
		if ( m_render_ctx != NULL )
		{
			wglDeleteContext( m_render_ctx );
			m_render_ctx = NULL;

			if ( m_text_list )
				glDeleteLists( m_text_list, 256 );
		}
		
		ReleaseDC( m_HWnd, m_device_ctx );
		m_device_ctx = NULL;
	}
}


//
// @brief	Render text
//
void OpenGLRender::RenderText( const char* str, float x, float y )
{
	glPushAttrib( GL_CURRENT_BIT|GL_ENABLE_BIT );

	glDisable( GL_DEPTH_TEST );
	glDisable( GL_LIGHTING );
	glDisable( GL_TEXTURE_2D );

	glRasterPos2f( x, y );
	glListBase( m_text_list ); 
	glCallLists( strlen(str), GL_UNSIGNED_BYTE, str ); 

	glPopAttrib();
}


//
// @brief	Handle windows message
//
void OpenGLRender::HandleMessage( UINT msg, WPARAM wParam, LPARAM lParam )
{
}



#ifdef SHADER_SUPPORT
//
// @brief	Load Shaders
//
bool OpenGLRender::LoadShaders( const char *vertexShaderFile, const char * fragmentShaderFile,
								uint &vtxShdrID, uint &fragShdrID, uint &shdrProgramID )
{
	if ( vertexShaderFile )
	{
		// Read shader
		char *vsstr = ReadFileToString( "VertexShader.glsl" );

		if ( !vsstr )
			return false;

		// Compile Vertex shader
		vtxShdrID = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource( vtxShdrID, 1, (const char**)&vsstr, NULL );
		glCompileShader( vtxShdrID );

		free( vsstr );

		// Debugging
		{
			int logLen = 0;
			char log[1024];
			PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC) wglGetProcAddress("glGetShaderInfoLog");
			glGetShaderInfoLog(vtxShdrID, 1024, &logLen, log);
			int i = 0;
		}
	} 

	if ( fragmentShaderFile )
	{
		// Read shader
		char *fsstr = ReadFileToString( "FragmentShader.glsl" );

		if ( !fsstr )
			return false;

		// Compile Fragment shader
		fragShdrID = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource( fragShdrID, 1, (const char**)&fsstr, NULL );
		glCompileShader( fragShdrID );

		free( fsstr );

		// Debugging
		{
			int logLen = 0;
			char log[1024];
			PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC) wglGetProcAddress("glGetShaderInfoLog");
			glGetShaderInfoLog(fragShdrID, 1024, &logLen, log);
			int i = 0;
		}
	}


	// Attach program
	if ( vertexShaderFile || fragmentShaderFile )
	{
		shdrProgramID = glCreateProgram();
		if ( vertexShaderFile )
			glAttachShader( shdrProgramID, vtxShdrID );
		if ( fragmentShaderFile )
			glAttachShader( shdrProgramID, fragShdrID );
		glLinkProgram( shdrProgramID );
		//glUseProgram( mShaderProgram );

		// Debugging
		{
			int logLen = 0;
			char log[1024];
			PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC) wglGetProcAddress("glGetProgramInfoLog");
			glGetProgramInfoLog(shdrProgramID, 1024, &logLen, log);
			int i = 0;
		}
	}

	return true;
}

//
// @brief	Bind shader
//
void OpenGLRender::BindShader( uint shdrProgramID )
{
	glUseProgram( shdrProgramID );
}

//
// @brief	Uninit shader
//
void OpenGLRender::UnInitShader( uint vtxShdrID, uint fragShdrID, uint shdrProgramID )
{
	if ( glIsShader( vtxShdrID ) )			glDeleteShader( vtxShdrID );
	if ( glIsShader( fragShdrID ) )			glDeleteShader( fragShdrID );
	if ( glIsProgram( shdrProgramID ) )		glDeleteProgram( shdrProgramID );

}
#endif


//
// @brief	Load a VBO
//
uint OpenGLRender::LoadVBO( uint data_size, void* data )
{
	// Bind & load the textures
	uint vbo;
	glGenBuffersARB( 1, &vbo );
	glBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo );
	glBufferDataARB( GL_ARRAY_BUFFER_ARB, data_size, data, GL_STATIC_DRAW_ARB );
	return vbo;
}

//
// @brief	UnInitVBO
//
void OpenGLRender::BindVBO( uint vbo )
{
	glBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo );
}

//
// @brief	UnloadVBO
//
void OpenGLRender::UnloadVBO( uint *vbo )
{
	if ( glIsBufferARB( *vbo ) )		glDeleteBuffersARB( 1, vbo );
}